using System;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;

namespace DbView
{
	public class MruMenu
	{
		protected MenuItem recentFileMenuItem;
		protected ClickedHandler clickedHandler;
		protected string registryKeyName;
		protected int numEntries = 0;
		protected int maxEntries = 8;
		protected int maxShortenPathLength = 0;

		#region MruMenuItem

		// The menu may display a shortened or otherwise invalid pathname
		// This class is used to store the actual filename, preferably as
		// a fully resolved name.
		//
		public class MruMenuItem : MenuItem
		{
			protected string filename;

			public MruMenuItem()
			{
				filename = "";
			}

			public MruMenuItem(string _filename, string entryname, EventHandler eventHandler)
				: base(entryname, eventHandler)
			{
				filename = _filename;
			}

			public string Filename
			{
				get
				{
					return filename;
				}
				set
				{
					filename = value;
				}
			}
		}
		#endregion

		/// <summary>
		/// MruMenu handles a most recently used (MRU) file list.
		/// 
		/// This class shows the MRU list in a popup menu. To display
		/// the MRU list "inline" use MruMenuInline.
		/// 
		/// The class will load the last set of files from the registry
		/// on construction and store them when instructed by the main
		/// program.
		/// 
		/// Internally, this class uses zero-based numbering for the items.
		/// The displayed numbers, however, will start with one.
		/// </summary>

		#region Construction

		protected MruMenu() { }

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, null, false, 8);
		}

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, int _maxEntries)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, null, false, _maxEntries);
		}

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, true, 8);
		}

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, int _maxEntries)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, true, _maxEntries);
		}

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, bool loadFromRegistry)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, loadFromRegistry, 8);
		}

		public MruMenu(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, bool loadFromRegistry, int _maxEntries)
		{
			this.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, loadFromRegistry, _maxEntries);
		}

		protected void Init(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, bool loadFromRegistry, int _maxEntries)
		{
			if (_recentFileMenuItem == null)
				throw new ArgumentNullException("recentFileMenuItem");

			if (_recentFileMenuItem.Parent == null)
				throw new ArgumentException("recentFileMenuItem is not part of a menu");

			this.recentFileMenuItem = _recentFileMenuItem;
			this.recentFileMenuItem.Checked = false;
			this.recentFileMenuItem.Enabled = false;
			this.recentFileMenuItem.DefaultItem = false;

			this.maxEntries = _maxEntries;
			this.clickedHandler = _clickedHandler;

			if (_registryKeyName != null)
			{
				this.RegistryKeyName = _registryKeyName;
				if (loadFromRegistry)
				{
					this.LoadFromRegistry();
				}
			}
		}
		#endregion

		#region Event Handling

		public delegate void ClickedHandler(int number, string filename);

		protected void OnClick(object sender, System.EventArgs e)
		{
			MruMenuItem menuItem = (MruMenuItem)sender;
			this.clickedHandler(menuItem.Index - this.StartIndex, menuItem.Filename);
		}
		#endregion

		#region Properties

		public virtual Menu.MenuItemCollection MenuItems
		{
			get
			{
				return this.recentFileMenuItem.MenuItems;
			}
		}

		public virtual int StartIndex
		{
			get
			{
				return 0;
			}
		}

		public virtual int EndIndex
		{
			get
			{
				return this.numEntries;
			}
		}

		public int NumEntries
		{
			get
			{
				return this.numEntries;
			}
		}

		public int MaxEntries
		{
			get
			{
				return this.maxEntries;
			}
			set
			{
				if (value > 16)
				{
					this.maxEntries = 16;
				}
				else
				{
					this.maxEntries = value < 4 ? 4 : value;

					int index = this.StartIndex + this.maxEntries;
					while (this.numEntries > this.maxEntries)
					{
						this.MenuItems.RemoveAt(index);
						this.numEntries--;
					}
				}
			}
		}

		public int MaxShortenPathLength
		{
			get
			{
				return this.maxShortenPathLength;
			}
			set
			{
				this.maxShortenPathLength = value < 16 ? 16 : value;
			}
		}

		#endregion

		#region Helper Methods

		protected virtual void Enable()
		{
			this.recentFileMenuItem.Enabled = true;
		}

		protected virtual void Disable()
		{
			this.recentFileMenuItem.Enabled = false;
			this.recentFileMenuItem.MenuItems.RemoveAt(0);
		}

		protected virtual void SetFirstFile(MenuItem menuItem)
		{
		}

		public void SetFirstFile(int number)
		{
			if (number > 0 && this.numEntries > 1 && number < this.numEntries)
			{
				MenuItem menuItem = this.MenuItems[this.StartIndex + number];
				menuItem.Index = this.StartIndex;
				this.SetFirstFile(menuItem);
				this.FixupPrefixes(0);
			}
		}

		public static string FixupEntryname(int number, string entryname)
		{
			if (number < 9)
				return "&" + (number + 1) + "  " + entryname;
			else if (number == 9)
				return "1&0" + "  " + entryname;
			else
				return (number + 1) + "  " + entryname;
		}

		protected void FixupPrefixes(int startNumber)
		{
			if (startNumber < 0)
				startNumber = 0;

			if (startNumber < this.maxEntries)
			{
				for (int i = this.StartIndex + startNumber; i < this.EndIndex; i++, startNumber++)
				{
					this.MenuItems[i].Text = FixupEntryname(startNumber, this.MenuItems[i].Text.Substring(startNumber == 9 ? 5 : 4));
				}
			}
		}
		#endregion

		#region Get Methods

		public int FindFilenameNumber(string filename)
		{
			if (filename == null)
				throw new ArgumentNullException("filename");

			if (filename.Length == 0)
				throw new ArgumentException("filename");

			if (this.numEntries > 0)
			{
				int number = 0;
				for (int i = this.StartIndex; i < this.EndIndex; i++, number++)
				{
					if (String.Compare(((MruMenuItem)this.MenuItems[i]).Filename, filename, true) == 0)
					{
						return number;
					}
				}
			}
			return -1;
		}

		public int FindFilenameMenuIndex(string filename)
		{
			int number = this.FindFilenameNumber(filename);
			return number < 0 ? -1 : this.StartIndex + number;
		}

		public int GetMenuIndex(int number)
		{
			if (number < 0 || number >= this.numEntries)
				throw new ArgumentOutOfRangeException("number");

			return this.StartIndex + number;
		}

		public string GetFileAt(int number)
		{
			if (number < 0 || number >= this.numEntries)
				throw new ArgumentOutOfRangeException("number");

			return ((MruMenuItem)this.MenuItems[this.StartIndex + number]).Filename;
		}

		public String[] GetFiles()
		{
			String[] filenames = new String[this.numEntries];

			int index = this.StartIndex;
			for (int i = 0; i < filenames.GetLength(0); i++, index++)
			{
				filenames[i] = ((MruMenuItem)this.MenuItems[index]).Filename;
			}

			return filenames;
		}

		// This is used for testing
		public String[] GetFilesFullEntryString()
		{
			String[] filenames = new String[this.numEntries];

			int index = this.StartIndex;
			for (int i = 0; i < filenames.GetLength(0); i++, index++)
			{
				filenames[i] = this.MenuItems[index].Text;
			}

			return filenames;
		}
		#endregion

		#region Add Methods

		public void SetFiles(String[] filenames)
		{
			this.RemoveAll();
			for (int i = filenames.GetLength(0) - 1; i >= 0; i--)
			{
				this.AddFile(filenames[i]);
			}
		}

		public void AddFiles(String[] filenames)
		{
			for (int i = filenames.GetLength(0) - 1; i >= 0; i--)
			{
				this.AddFile(filenames[i]);
			}
		}

		// Shortens a pathname by either removing consecutive components of a path
		// and/or by removing characters from the end of the filename and replacing
		// then with three elipses (...)
		//
		// In all cases, the root of the passed path will be preserved in it's entirety.
		//
		// If a UNC path is used or the pathname and maxLength are particularly short,
		// the resulting path may be longer than maxLength.
		//
		// This method expects fully resolved pathnames to be passed to it.
		// (Use Path.GetFullPath() to obtain this.)
		//
		static public string ShortenPathname(string pathname, int maxLength)
		{
			if (pathname.Length <= maxLength)
				return pathname;

			string root = Path.GetPathRoot(pathname);
			if (root.Length > 3)
				root += Path.DirectorySeparatorChar;

			String[] elements = pathname.Substring(root.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

			int filenameIndex = elements.GetLength(0) - 1;

			if (elements.GetLength(0) == 1) // pathname is just a root and filename
			{
				if (elements[0].Length > 5) // long enough to shorten
				{
					// if path is a UNC path, root may be rather long
					if (root.Length + 6 >= maxLength)
					{
						return root + elements[0].Substring(0, 3) + "...";
					}
					else
					{
						return pathname.Substring(0, maxLength - 3) + "...";
					}
				}
			}
			else if ((root.Length + 4 + elements[filenameIndex].Length) > maxLength) // pathname is just a root and filename
			{
				root += "...\\";

				int len = elements[filenameIndex].Length;
				if (len < 6)
					return root + elements[filenameIndex];

				if ((root.Length + 6) >= maxLength)
				{
					len = 3;
				}
				else
				{
					len = maxLength - root.Length - 3;
				}
				return root + elements[filenameIndex].Substring(0, len) + "...";
			}
			else if (elements.GetLength(0) == 2)
			{
				return root + "...\\" + elements[1];
			}
			else
			{
				int len = 0;
				int begin = 0;

				for (int i = 0; i < filenameIndex; i++)
				{
					if (elements[i].Length > len)
					{
						begin = i;
						len = elements[i].Length;
					}
				}

				int totalLength = pathname.Length - len + 3;
				int end = begin + 1;

				while (totalLength > maxLength)
				{
					if (begin > 0)
						totalLength -= elements[--begin].Length - 1;

					if (totalLength <= maxLength)
						break;

					if (end < filenameIndex)
						totalLength -= elements[++end].Length - 1;

					if (begin == 0 && end == filenameIndex)
						break;
				}

				// assemble final string

				for (int i = 0; i < begin; i++)
				{
					root += elements[i] + '\\';
				}

				root += "...\\";

				for (int i = end; i < filenameIndex; i++)
				{
					root += elements[i] + '\\';
				}

				return root + elements[filenameIndex];
			}
			return pathname;
		}

		public void AddFile(string filename)
		{
			string pathname = Path.GetFileName(filename); // .GetFullPath(filename);
			this.AddFile(pathname, ShortenPathname(pathname, this.MaxShortenPathLength));
		}

		public void AddFile(string filename, string entryname)
		{
			if (filename == null)
				throw new ArgumentNullException("filename");

			if (filename.Length == 0)
				throw new ArgumentException("filename");

			if (this.numEntries > 0)
			{
				int index = this.FindFilenameMenuIndex(filename);
				if (index >= 0)
				{
					this.SetFirstFile(index - this.StartIndex);
					return;
				}
			}

			if (this.numEntries < this.maxEntries)
			{
				MruMenuItem menuItem = new MruMenuItem(filename, FixupEntryname(0, entryname), new System.EventHandler(this.OnClick));
				this.MenuItems.Add(this.StartIndex, menuItem);
				this.SetFirstFile(menuItem);

				if (this.numEntries++ == 0)
				{
					this.Enable();
				}
				else
				{
					this.FixupPrefixes(1);
				}
			}
			else if (this.numEntries > 1)
			{
				MruMenuItem menuItem = (MruMenuItem)this.MenuItems[this.StartIndex + this.numEntries - 1];
				menuItem.Text = FixupEntryname(0, entryname);
				menuItem.Filename = filename;
				menuItem.Index = this.StartIndex;
				this.SetFirstFile(menuItem);
				this.FixupPrefixes(1);
			}
		}

		#endregion

		#region Remove Methods

		public void RemoveFile(int number)
		{
			if (number >= 0 && number < this.numEntries)
			{
				if (--this.numEntries == 0)
				{
					this.Disable();
				}
				else
				{
					int startIndex = this.StartIndex;
					if (number == 0)
					{
						this.SetFirstFile(this.MenuItems[startIndex + 1]);
					}

					this.MenuItems.RemoveAt(startIndex + number);

					if (number < this.numEntries)
					{
						this.FixupPrefixes(number);
					}
				}
			}
		}

		public void RemoveFile(string filename)
		{
			if (this.numEntries > 0)
			{
				this.RemoveFile(this.FindFilenameNumber(filename));
			}
		}

		public void RemoveAll()
		{
			if (this.numEntries > 0)
			{
				for (int index = this.EndIndex - 1; index > this.StartIndex; index--)
				{
					this.MenuItems.RemoveAt(index);
				}
				this.Disable();
				this.numEntries = 0;
			}
		}

		#endregion

		#region Registry Methods

		public string RegistryKeyName
		{
			get
			{
				return this.registryKeyName;
			}
			set
			{
				this.registryKeyName = value.Trim();
				if (this.registryKeyName.Length == 0)
				{
					this.registryKeyName = null;
				}
			}
		}

		public void LoadFromRegistry(string keyName)
		{
			this.RegistryKeyName = keyName;
			this.LoadFromRegistry();
		}

		public void LoadFromRegistry()
		{
			if (this.registryKeyName != null)
			{
				this.RemoveAll();

				RegistryKey regKey = Registry.CurrentUser.OpenSubKey(this.registryKeyName);
				if (regKey != null)
				{
					this.maxEntries = (int)regKey.GetValue("max", this.maxEntries);

					for (int number = this.maxEntries; number > 0; number--)
					{
						string filename = (String)regKey.GetValue("File" + number.ToString());
						if (filename != null)
							this.AddFile(filename);
					}

					regKey.Close();
				}
			}
		}

		public void SaveToRegistry(string keyName)
		{
			this.RegistryKeyName = keyName;
			this.SaveToRegistry();
		}

		public void SaveToRegistry()
		{
			if (this.registryKeyName != null)
			{
				RegistryKey regKey = Registry.CurrentUser.CreateSubKey(this.registryKeyName);
				if (regKey != null)
				{
					regKey.SetValue("max", this.maxEntries);

					int number = 1;
					int i = this.StartIndex;
					for (; i < this.EndIndex; i++, number++)
					{
						regKey.SetValue("File" + number.ToString(), ((MruMenuItem)this.MenuItems[i]).Filename);
					}

					for (; number <= 16; number++)
					{
						regKey.DeleteValue("File" + number.ToString(), false);
					}

					regKey.Close();
				}
			}
		}

		#endregion
	}

	public class MruMenuInline : MruMenu
	{
		protected MenuItem firstMenuItem;

		/// <summary>
		/// MruMenuInline shows the MRU list inline (without a popup)
		/// </summary>

		#region Construction

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, null, false, 8);
		}

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, int _maxEntries)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, null, false, _maxEntries);
		}

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, true, 8);
		}

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, int _maxEntries)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, true, _maxEntries);
		}

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, bool loadFromRegistry)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, loadFromRegistry, 8);
		}

		public MruMenuInline(MenuItem _recentFileMenuItem, ClickedHandler _clickedHandler, string _registryKeyName, bool loadFromRegistry, int _maxEntries)
		{
			maxShortenPathLength = 128;
			firstMenuItem = _recentFileMenuItem;
			base.Init(_recentFileMenuItem, _clickedHandler, _registryKeyName, loadFromRegistry, _maxEntries);
		}
		#endregion

		#region Overridden Properties

		public override Menu.MenuItemCollection MenuItems
		{
			get
			{
				return firstMenuItem.Parent.MenuItems;
			}
		}

		public override int StartIndex
		{
			get
			{
				return firstMenuItem.Index;
			}
		}

		public override int EndIndex
		{
			get
			{
				return StartIndex + numEntries;
			}
		}
		#endregion

		#region Overridden Methods

		protected override void Enable()
		{
			MenuItems.Remove(recentFileMenuItem);
		}

		protected override void SetFirstFile(MenuItem menuItem)
		{
			firstMenuItem = menuItem;
		}

		protected override void Disable()
		{
			MenuItems.Add(firstMenuItem.Index, recentFileMenuItem);
			MenuItems.Remove(firstMenuItem);
			firstMenuItem = recentFileMenuItem;
		}
		#endregion
	}
}